export const suspend_t = Symbol('suspend_t')

export interface IBuffer {
    current():number;
    seek(idx:number):void;
    truncate(seeker:number):void;
    getByte():number;
}

class DataIterator {
    _type : MType
    _seeker : number
    _data : any
    constructor(type:MType, parser:DataParser) {
        this._type = type
        this._seeker = parser.buffer.current()
    }

    getData():any {
        let _reactor = this._type._reactor
        if(_reactor){
            try{
                return _reactor(this._data)
            }catch(ex){
                console.error('_reactor', ex)
            }
        }
        return this._data
    }

    next(parser:DataParser):DataIterator | symbol | boolean {
        console.trace("debugger")
        throw 'nonimplement'
    }
}



class MType {
    _invalidFlag?:boolean
    _reactor?:(data:any)=>any
    constructor(){
    }

    getIter(parser:DataParser):DataIterator {
        console.trace("debugger")
        throw 'nonimplement'
    }

    pack(data:any):Uint8Array {
        console.trace("debugger")
        throw 'nonimplement'
    }

    unpack(buffer:IBuffer):DataParser {
        return new DataParser(buffer, this);
    }
}


class AllType extends MType {
    _types:MType[]
    constructor(...types:MType[]){
        super()
        this._types = types
    }

    push(...types:MType[]) {
        this._types = this._types.concat(types)
    }

    getIter(parser:DataParser):DataIterator {
        return new MAllDataIterator(this, parser)
    }

    addType(type:MType, reactor?:(datas:any)=>any):void {
        this._types.push(type);
        type._reactor = reactor
    }

    setReactor (reactor:(datas:any)=>any): any {
        this._reactor = reactor
    }
}

class MAllDataIterator extends DataIterator {
    _index:number
    _lastIter:DataIterator
    constructor(type:AllType, parser:DataParser) {
        super(type, parser);
        this._index = -1
        this._lastIter = null
        this._data = []
    }

    next(parser:DataParser):DataIterator | symbol | boolean {
        if(this._lastIter) {
            if(!parser.currentState) return false
            this._data.push(this._lastIter.getData())
        }
        this._index++
        if(this._index == (this._type as AllType)._types.length) {
            return true
        }

        let t = (this._type as AllType)._types[this._index]
        let it = t.getIter(parser)
        this._lastIter = it
        return it
    }
}

export class AnyType extends MType {
    _types:MType[]
    constructor(...types:MType[]){
        super()
        this._types = types
    }

    push(...types:MType[]) {
        this._types = this._types.concat(types)
    }

    getIter(parser:DataParser):DataIterator {
        return new MAnyDataIterator(this, parser)
    }
    
    addType(type:MType, reactor?:(datas:any)=>void):cancellable_t {
        this._types.push(type);
        if(reactor){
            type._reactor = reactor
        }
        return ()=>{
            type._invalidFlag = true;
            this._types = this._types.filter(t=>t!==type)
        }
    }
}

class MAnyDataIterator extends DataIterator {
    _index:number
    _lastIter:DataIterator
    _data:any
    constructor(type:AnyType, parser:DataParser) {
        super(type, parser);
        this._index = -1
        this._lastIter = null
    }

    next(parser:DataParser):DataIterator | symbol | boolean {
        if(this._lastIter) {
            if(parser.currentState) {
                this._data = [this._index, this._lastIter.getData()]
                return true
            }
            parser.buffer.seek(this._lastIter._seeker)
        }
        this._index++
        if(this._index == (this._type as AnyType)._types.length) {
            return false
        }

        let t = (this._type as AnyType)._types[this._index]
        let it = t.getIter(parser)
        this._lastIter = it
        return it
    }
}


export class DataParser {
    _iterStk : DataIterator[]
    _iter: DataIterator
    
    buffer : IBuffer
    currentState:boolean
    _type : MType;
    _data : any;
    
    constructor(buffer:IBuffer, type:MType) {
        this.buffer = buffer
        this.currentState = true
        this._iterStk = []
        this._type = type;
        this._iter = this._type.getIter(this);
    }

    getData(): any {
        return this._data
    }

    parse(): symbol|boolean{
        for(;true;){
            let next:DataIterator|symbol|boolean;
            if(this._iter._type._invalidFlag) {
                next = false
            } else {
                next = this._iter.next(this)
            }
            if(next instanceof DataIterator) {
                this._iterStk.push(this._iter)
                this._iter = next
            } else {
                if(next === suspend_t){
                    return suspend_t
                }
                if(this._iterStk.length == 0) {
                    if(next == true){
                        this._data = this._iter.getData()
                        this.buffer.truncate(this.buffer.current());
                    } else {
                        this.buffer.seek(this._iter._seeker);
                        this.buffer.getByte(); // index = index+1, 
                        this.buffer.truncate(this.buffer.current());
                    }
                    this._iter = this._type.getIter(this);
                    return next
                } else {
                    this.currentState = (next == true)
                    this._iter = this._iterStk.pop()
                }
            }
        }
    }
}

export class BytesBuffer implements IBuffer {
    _bytes : Uint8Array
    _index : number
    _from : number
    constructor(bytes?:Uint8Array) {
        if(!bytes){
            bytes = new Uint8Array([]);
        }
        this._bytes = bytes
        this._index = 0
        this._from = 0
    }

    // 当前位置
    current():number {
        return this._from+this._index
    }

    // 设置到当前位置
    seek(seeker:number):void {
        this._index = seeker - this._from
        if(this._index < 0) throw Error(`seek index of ${this._index}`)
    }

    // 切除到位置
    truncate(seeker:number):void {
        this._from = seeker
        this._index = 0
    }

    // 前进一位, 取得字节
    getByte():number {
        let idx = this._from+this._index
        if(idx >= this._bytes.length){
            return null
        }
        this._index++
        return this._bytes[idx]
    }

    push(...bytes:number[]):void {
        var thisbuff1 = new Uint8Array(this._bytes.length + bytes.length);
        thisbuff1.set(this._bytes);
        thisbuff1.set(bytes, this._bytes.length);
        this._bytes = thisbuff1
    }

    getBytes():Uint8Array {
        return this._bytes
    }
}

class ByteTypeIterator extends DataIterator {
    next(parser:DataParser) {
        let byte = parser.buffer.getByte()
        if(byte == null) {
            return suspend_t
        }
        this._data = byte
        return true
    }
}

class MByteType extends MType {
    constructor() {
        super()
    }

    pack(byte:number):Uint8Array {
        return new Uint8Array([byte]);
    }

    getIter(parser:DataParser):DataIterator {
        return new ByteTypeIterator(this, parser)
    }
}

class CountBytesIterator extends DataIterator {
    _index:number = 0
    _len:number
    constructor(type:MType, parser:DataParser) {
        super(type, parser)
        this._len = (this._type as CountBytes).len
        this._data = []
    }
    next(parser:DataParser) {
        while(this._index !== this._len) {
            let byte = parser.buffer.getByte()
            if(byte == null) return suspend_t
            this._data.push(byte);
            this._index++;
        }
        return true
    }
}

class CountBytes extends MType {
    len:number
    constructor(n:number) {
        super()
        this.len = n
    }

    getIter(parser:DataParser):DataIterator {
        return new CountBytesIterator(this, parser);
    }
}

class MatchBytesIterator extends DataIterator {
    _index:number
    constructor(type:MType, parser:DataParser) {
        super(type, parser)
        this._index = 0
    }
    next(parser:DataParser) {
        let bs = (this._type as MatchBytes).bytes

        while(this._index !== bs.length){
            let byte = parser.buffer.getByte()
            if(byte == null) return suspend_t
            if(byte !== bs[this._index]) {
                return false
            }
            this._index++
        }
        this._data = bs
        return true
    }
}

class MatchBytes extends MType {
    bytes:number[]
    constructor(bytes:number[]){
        super()
        this.bytes = bytes
    }
    getIter(parser:DataParser):DataIterator {
        return new MatchBytesIterator(this, parser)
    }
}

class TypePredIterator extends DataIterator {
    getData():any {
        return null
    }

    next(parser:DataParser):DataIterator | symbol | boolean {
        let pred = (this._type as TypePred)._pred
        try{
            if(pred()) return true
            return false
        } catch(ex){
            console.error("TypePredIterator", ex);
        }
        return false
    }
}

class TypePred extends MType {
    _pred:()=>boolean
    constructor(pred:()=>boolean) {
        super()
        this._pred = pred
    }

    getIter(parser:DataParser):DataIterator {
        return new TypePredIterator(this, parser);
    }
}

class LazyType extends MType {
    _typeGen:()=>MType
    constructor(typeGen:()=>MType) {
        super()
        this._typeGen = typeGen
    }

    getIter(parser:DataParser):DataIterator {
        let t = this._typeGen()
        t._reactor = this._reactor
        return t.getIter(parser);
    }
}

export const pred_of = (pred:()=>boolean) => {
    return new TypePred(pred)
}

export const match_of = (bytes:number[]) => {
    return new MatchBytes(bytes);
}

export const count_of = (len:number) => {
    return new CountBytes(len);
}

export const all_of = (...types:MType[]) => {
    return new AllType(...types);
}

export const any_of = (...types:MType[]) => {
    return new AnyType(...types);
}

export const lazy_of = (typeGen:()=>MType) => {
    return new LazyType(typeGen);
}